using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Comms;
using Engine;
using Game;

namespace RuthlessConquest {
    public class Server : IDisposable {
        public const int PortNumber = 40102;
        public const int StepsPerSecond = 60;
        public const float StepDuration = 1f / 60f;
        public const int StepsPerTick = 30;
        public const float TickDuration = 0.5f;
        public const string fName = "RCServer";
        public Thread Thread;
        public ServerConfig Config;
        public byte[] CachedGameListMessageBytes;
        public double CachedGameListMessageTime;
        public double TotalLoadTime;
        public double LastLoadReportTime;
        public Dictionary<IPEndPoint, double> RecentlySeenUsers = new();
        public DynamicArray<IPEndPoint> ToRemove = [];
        public Peer Peer { get; }
        public bool IsDedicatedServer { get; }
        public bool IsUsingInProcessTransmitter { get; }
        public bool IsPaused { get; set; }
        public bool IsDisposing { get; set; }
        public DynamicArray<ServerGame> Games { get; } = [];

        public Server(bool isDedicatedServer, bool useInProcessTransmitter) {
            Log.Information(
                string.Format(
                    LanguageControl.Get(fName, "1"),
                    DateTime.Now.ToUniversalTime().ToString(CultureInfo.CurrentCulture),
                    VersionsManager.Version
                )
            );
            IsDedicatedServer = isDedicatedServer;
            IsUsingInProcessTransmitter = useInProcessTransmitter;
            Config = new ServerConfig(this);
            ITransmitter transmitter = !useInProcessTransmitter
                ? new DiagnosticTransmitter(new UdpTransmitter(PortNumber))
                : new DiagnosticTransmitter(new InProcessTransmitter());
            Log.Information(string.Format(LanguageControl.Get(fName, "2"), transmitter.Address));
            Peer = new Peer(transmitter);
            Peer.Settings.SendPeerConnectDisconnectNotifications = false;
            Peer.Comm.Settings.ResendPeriods = [0.5f, 0.5f, 1f, 1.5f, 2f];
            Peer.Comm.Settings.MaxResends = 20;
            Peer.Settings.KeepAlivePeriod = IsUsingInProcessTransmitter ? float.PositiveInfinity : 5f;
            Peer.Settings.KeepAliveResendPeriod = IsUsingInProcessTransmitter ? float.PositiveInfinity : 2f;
            Peer.Settings.ConnectionLostPeriod = IsUsingInProcessTransmitter ? float.PositiveInfinity : 30f;
            Peer.Settings.ConnectTimeOut = 6f;
            Peer.Error += delegate(Exception e) {
                if (e is ProtocolViolationException) {
                    Log.Warning(e);
                }
                else {
                    Log.Error(e);
                }
            };
            Peer.PeerDiscoveryRequest += delegate(Packet p) {
                if (!IsDisposing) {
                    if (p.Bytes.Length < 4) {
                        throw new ProtocolViolationException(LanguageControl.Get(fName, "3"));
                    }
                    HandlePeerDiscovery(new Version(p.Bytes, 0), p.Address);
                }
            };
            Peer.ConnectRequest += delegate(PeerPacket p) {
                if (!IsDisposing) {
                    Message message = Message.Read(p.Bytes);
                    if (!(message is CreateGameMessage message2)) {
                        if (!(message is JoinGameMessage message3)) {
                            throw new ProtocolViolationException(LanguageControl.Get(fName, "3"));
                        }
                        Handle(message3, p.PeerData);
                    }
                    else {
                        Handle(message2, p.PeerData);
                    }
                }
            };
            Peer.PeerDisconnected += delegate(PeerData peerData) {
                if (!IsDisposing) {
                    HandleDisconnect(peerData);
                }
            };
            Peer.DataMessageReceived += delegate(PeerPacket p) {
                if (!IsDisposing) {
                    Message message4 = Message.Read(p.Bytes);
                    if (!(message4 is StartGameMessage message5)) {
                        if (!(message4 is PlayerOrdersMessage message6)) {
                            if (!(message4 is GameImageMessage message7)) {
                                if (!(message4 is GameStateMessage message8)) {
                                    if (!(message4 is GameStateHashMessage message9)) {
                                        throw new ProtocolViolationException(LanguageControl.Get(fName, "3"));
                                    }
                                    Handle(message9, p.PeerData);
                                }
                                else {
                                    Handle(message8, p.PeerData);
                                }
                            }
                            else {
                                Handle(message7, p.PeerData);
                            }
                        }
                        else {
                            Handle(message6, p.PeerData);
                        }
                    }
                    else {
                        Handle(message5, p.PeerData);
                    }
                }
            };
            Peer.Start();
            if (IsDedicatedServer) {
                Run();
                return;
            }
            Window.Activated += WindowActivated;
            Window.Deactivated += WindowDeactivated;
            Window.Closed += WindowClosed;
            Thread = new Thread(Run);
            Thread.IsBackground = true;
            Thread.Start();
        }

        public void Dispose() {
            Window.Activated -= WindowActivated;
            Window.Deactivated -= WindowDeactivated;
            Window.Closed -= WindowClosed;
            IsDisposing = true;
        }

        public ServerHumanPlayer GetPlayer(PeerData peerData) => peerData.Tag as ServerHumanPlayer;

        public string GetStatsString() {
            int count = Games.Count;
            int num = Games.SelectMany(gd => gd.Players).Count();
            return string.Format(LanguageControl.Get(fName, "4"), count, num);
        }

        public void Run() {
            if (IsDedicatedServer) {
                //Dispatcher.Initialize();
                Task.Factory.StartNew(
                    delegate {
                        while (true) {
                            Dispatcher.ExecuteActionsOnCurrentThread();
                            Task.Delay(100).Wait();
                        }
                    },
                    TaskCreationOptions.LongRunning
                );
            }
            double num = Time.RealTime;
            while (!IsDisposing) {
                double realTime = Time.RealTime;
                Config.Run();
                lock (Peer.Lock) {
                    double realTime2 = Time.RealTime;
                    ToRemove.Clear();
                    foreach (KeyValuePair<IPEndPoint, double> recentlySeenUser in RecentlySeenUsers) {
                        if (realTime2 > recentlySeenUser.Value + 60.0) {
                            ToRemove.Add(recentlySeenUser.Key);
                        }
                    }
                    foreach (IPEndPoint item in ToRemove) {
                        RecentlySeenUsers.Remove(item);
                    }
                }
                while (Time.RealTime >= num) {
                    num += 0.5;
                    if (IsPaused) {
                        continue;
                    }
                    lock (Peer.Lock) {
                        ServerGame[] array = Games.ToArray();
                        for (int i = 0; i < array.Length; i++) {
                            array[i].Run();
                        }
                    }
                }
                double realTime3 = Time.RealTime;
                double num2 = num - Time.RealTime;
                if (num2 > 0.0) {
                    Thread.Sleep(MathUtils.Clamp((int)(0.5 * num2 * 1000.0), 10, 1000));
                }
                double realTime4 = Time.RealTime;
                TotalLoadTime += realTime3 - realTime;
                if (LastLoadReportTime == 0.0
                    || realTime4 >= LastLoadReportTime + 60.0) {
                    if (LastLoadReportTime > 0.0
                        && Peer.Comm.Transmitter is DiagnosticTransmitter diagnosticTransmitter) {
                        Log.Information(
                            string.Format(
                                LanguageControl.Get(fName, "5"),
                                (TotalLoadTime / (realTime4 - LastLoadReportTime)).ToString("0.000000"),
                                GetStatsString(),
                                diagnosticTransmitter.Stats.BytesSent / 1024,
                                diagnosticTransmitter.Stats.BytesReceived / 1024,
                                RecentlySeenUsers.Count
                            )
                        );
                    }
                    LastLoadReportTime = realTime4;
                    TotalLoadTime = 0.0;
                }
            }
            Peer.DisconnectAllPeers();
            Task.Run(
                delegate {
                    Thread.Sleep(1000);
                    Peer.Dispose();
                }
            );
        }

        public void HandlePeerDiscovery(Version version, IPEndPoint address) {
            if (version.GetNetworkProtocolVersion() == VersionsManager.Version.GetNetworkProtocolVersion()) {
                double realTime = Time.RealTime;
                RecentlySeenUsers[address] = realTime;
                if (CachedGameListMessageBytes == null
                    || realTime > CachedGameListMessageTime + 0.5) {
                    GameListMessage gameListMessage = new() {
                        ServerPriority = !Config.ShutdownSequence ? Config.ServerPriority : 0, ServerName = Config.ServerName
                    };
                    int num = 0;
                    foreach (ServerGame item in Games.OrderBy(g => g.Tick)) {
                        if (num > 40) {
                            break;
                        }
                        gameListMessage.GameDescriptions.Add(
                            new GameDescription {
                                GameId = item.GameId,
                                HumanPlayersCount = item.Players.Count(p => p.Faction != Faction.None),
                                SpectatorsCount = item.Players.Count(p => p.Faction == Faction.None),
                                TicksSinceStart = item.GameStartTick >= 0 ? item.Tick - item.GameStartTick : -1,
                                CreationParameters = item.CreationParameters,
                                GameImage = item.GameImage
                            }
                        );
                        num++;
                    }
                    CachedGameListMessageBytes = Message.Write(gameListMessage);
                    CachedGameListMessageTime = realTime;
                }
                Peer.RespondToDiscovery(address, DeliveryMode.Unreliable, CachedGameListMessageBytes);
            }
            else {
                MemoryStream memoryStream = new();
                memoryStream.WriteByte(0);
                memoryStream.Write(VersionsManager.Version.ToByteArray(), 0, 4);
                Peer.RespondToDiscovery(address, DeliveryMode.Raw, memoryStream.ToArray());
            }
        }

        public void Handle(CreateGameMessage message, PeerData peerData) {
            if (VerifyPlayerName(message.CreationParameters.CreatingPlayerName)) {
                RecentlySeenUsers[peerData.Address] = Time.RealTime;
                if (Config.ShutdownSequence
                    || Config.ServerPriority <= 0) {
                    Peer.RefuseConnect(
                        peerData,
                        Message.Write(new RefusedMessage { Reason = "Server restarting, please wait a while and try again." })
                    );
                }
                else if (Games.Count < 400) {
                    ServerGame serverGame = new(this, message, peerData);
                    Games.Add(serverGame);
                    Peer.AcceptConnect(
                        peerData,
                        Message.Write(new GameCreatedMessage { GameId = serverGame.GameId, CreationParameters = message.CreationParameters })
                    );
                    Log.Information(
                        string.Format(
                            LanguageControl.Get(fName, "6"),
                            message.CreationParameters.CreatingPlayerName,
                            peerData.Address,
                            message.CreationParameters.CreatingPlayerPlatform,
                            message.ReceivedVersion,
                            serverGame.GameId,
                            GetStatsString()
                        )
                    );
                }
                else {
                    Log.Warning(
                        string.Format(
                            LanguageControl.Get(fName, "7"),
                            peerData.Address,
                            message.CreationParameters.CreatingPlayerPlatform,
                            message.ReceivedVersion,
                            Games.Count
                        )
                    );
                    Peer.RefuseConnect(
                        peerData,
                        Message.Write(new RefusedMessage { Reason = "Too many games in progress, please wait a while and try again." })
                    );
                }
            }
            else {
                Log.Warning(
                    string.Format(
                        LanguageControl.Get(fName, "8"),
                        peerData.Address,
                        message.CreationParameters.CreatingPlayerPlatform,
                        message.ReceivedVersion,
                        message.CreationParameters.CreatingPlayerName
                    )
                );
                Peer.RefuseConnect(peerData, Message.Write(new RefusedMessage { Reason = "Please change your nickname in Settings." }));
            }
        }

        public void Handle(JoinGameMessage message, PeerData peerData) {
            if (VerifyPlayerName(message.PlayerName)) {
                RecentlySeenUsers[peerData.Address] = Time.RealTime;
                ServerGame serverGame = Games.FirstOrDefault(g => g.GameId == message.GameId);
                if (serverGame != null) {
                    serverGame.Handle(message, peerData);
                    return;
                }
                Peer.RefuseConnect(peerData, Message.Write(new RefusedMessage { Reason = "Game does not exist" }));
            }
            else {
                Log.Warning(
                    string.Format(
                        LanguageControl.Get(fName, "9"),
                        peerData.Address,
                        message.PlayerPlatform,
                        message.ReceivedVersion,
                        message.PlayerName
                    )
                );
                Peer.RefuseConnect(peerData, Message.Write(new RefusedMessage { Reason = "Please change your nickname in Settings." }));
            }
        }

        public void Handle(StartGameMessage message, PeerData peerData) {
            RecentlySeenUsers[peerData.Address] = Time.RealTime;
            ServerHumanPlayer player = GetPlayer(peerData);
            if (player != null) {
                player.Game.Handle(message, player);
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "10"), peerData.Address));
        }

        public void Handle(PlayerOrdersMessage message, PeerData peerData) {
            RecentlySeenUsers[peerData.Address] = Time.RealTime;
            ServerHumanPlayer player = GetPlayer(peerData);
            if (player != null) {
                player.Game.Handle(message, player);
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "11"), peerData.Address));
        }

        public void Handle(GameImageMessage message, PeerData peerData) {
            RecentlySeenUsers[peerData.Address] = Time.RealTime;
            ServerHumanPlayer player = GetPlayer(peerData);
            if (player != null) {
                player.Game.Handle(message, player);
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "12"), peerData.Address));
        }

        public void Handle(GameStateMessage message, PeerData peerData) {
            RecentlySeenUsers[peerData.Address] = Time.RealTime;
            ServerHumanPlayer player = GetPlayer(peerData);
            if (player != null) {
                player.Game.Handle(message, player);
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "13"), peerData.Address));
        }

        public void Handle(GameStateHashMessage message, PeerData peerData) {
            RecentlySeenUsers[peerData.Address] = Time.RealTime;
            ServerHumanPlayer player = GetPlayer(peerData);
            if (player != null) {
                player.Game.Handle(message, player);
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "14"), peerData.Address));
        }

        public void HandleDisconnect(PeerData peerData) {
            ServerHumanPlayer player = GetPlayer(peerData);
            if (player != null) {
                player.Game.HandleDisconnect(player);
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "15"), peerData.Address));
        }

        public void WindowActivated() {
            if (IsUsingInProcessTransmitter && !IsDedicatedServer) {
                IsPaused = false;
            }
        }

        public void WindowDeactivated() {
            if (IsUsingInProcessTransmitter && !IsDedicatedServer) {
                IsPaused = true;
            }
        }

        public void WindowClosed() {
            Dispose();
            if (!IsUsingInProcessTransmitter) {
                Thread.Sleep(750);
            }
        }

        public static bool VerifyPlayerName(string playerName) {
            if (NamesManager.ValidatePlayerName(playerName) != null) {
                return false;
            }
            if (playerName != "Kaalus") {
                return playerName.Replace(" ", "").ToLower() != "kaalus";
            }
            return true;
        }
    }
}